package com.atom.map.util

import java.lang.ref.WeakReference
import java.util.*


class GroupedLinkedMap<K, V> {

    private class LinkedEntry<K, V> {
        var key: K? = null

        var value: WeakReference<V>? = null

        var next: LinkedEntry<K, V>

        var prev: LinkedEntry<K, V>

        constructor() : this(null)

        constructor(key: K?) {
            this.next = this
            this.prev = this
            this.key = key
        }

        fun key(): K? {
            return this.key
        }

        fun value(): V? {
            return value?.get()
        }

        fun set(value: V) {
            this.value = WeakReference(value)
        }

        fun remove(): V? {
            val get = this.value?.get()
            this.value = null
            return get
        }
    }

    private val head = LinkedEntry<K, V>()

    private val keyToEntry: MutableMap<K, LinkedEntry<K, V>> = HashMap()

    fun put(key: K, value: V): V? {
        var entry = keyToEntry[key]
        var old: V? = null
        if (entry == null) {
            entry = LinkedEntry(key)
            makeTail(entry)
            keyToEntry[key] = entry
        }
        old = entry.value()
        entry.set(value)
        return old
    }

    fun get(key: K): V? {
        var entry = keyToEntry[key]
        if (entry == null) {
            entry = LinkedEntry(key)
            keyToEntry[key] = entry
        }
        makeHead(entry)
        return entry.value()
    }

    fun remove(key: K): V? {
        val entry = keyToEntry.remove(key)
        if (entry != null) {
            removeEntry(entry)
        }
        return entry?.value()
    }

    fun containsKey(key: K): Boolean = keyToEntry.containsKey(key)

    fun removeLast(): V? {
        var last: LinkedEntry<K, V> = head.prev
        while (last != head) {
            val removed = last.remove()
            if (removed != null) {
                return removed
            } else {
                removeEntry(last)
                keyToEntry.remove(last.key)
            }
            last = last.prev
        }
        return null
    }

    fun size(): Int {
        return keyToEntry.size
    }

    private fun <K, V> updateEntry(entry: LinkedEntry<K, V>) {
        entry.next.prev = entry
        entry.prev.next = entry
    }

    private fun <K, V> removeEntry(entry: LinkedEntry<K, V>) {
        entry.prev.next = entry.next
        entry.next.prev = entry.prev
    }

    // Make the entry the most recently used item.
    private fun makeHead(entry: LinkedEntry<K, V>) {
        removeEntry(entry)
        entry.prev = head
        entry.next = head.next
        updateEntry(entry)
    }

    // Make the entry the least recently used item.
    private fun makeTail(entry: LinkedEntry<K, V>) {
        removeEntry(entry)
        entry.prev = head.prev
        entry.next = head
        updateEntry(entry)
    }

    override fun toString(): String {
        val sb = StringBuilder("GroupedLinkedMap( ")
        var current: LinkedEntry<K, V> = head.next
        var hadAtLeastOneItem = false
        while (current != head) {
            hadAtLeastOneItem = true
            sb.append('{').append(current.key).append(':').append(current.value()).append("}, ")
            current = current.next
        }
        if (hadAtLeastOneItem) {
            sb.delete(sb.length - 2, sb.length)
        }
        return sb.append(" )").toString()
    }
}